﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Reflection;
using System.Threading;
using System.Windows.Threading;

using Invini.Tools;

namespace Invini.Training
{
    [UsableDuringInitialization(true)]
    [ContentProperty("Triggers")]
    public class TrainingScript : ITrainingScript, IResources
    {
        public class TrainingTriggersCollection : List<TrainingTriggerBase> { }

        public event TrainingScriptEventHandler Completed;
        public event TrainingTriggerEventHandler TriggerConditionSatisfied;
        public event TrainingTriggerEventHandler TriggerStarted;
        public event TrainingTriggerEventHandler TriggerCompleted;

        [Ambient]
        public ResourceDictionary Resources
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public string Title
        {
            get;
            set;
        }

        public string Description
        {
            get;
            set;
        }

        public bool IsHidden
        {
            get;
            set;
        }

        public bool IsRunning
        {
            get;
            private set;
        }

        public TrainingTriggersCollection Triggers
        {
            get;
            set;
        }

        private Dispatcher uiDispatcher;
        private TrainingTriggerBase nextTrigger;
        private TrainingTriggerBase currentTrigger;
        private Thread scriptThread;
        private SemaphoreSlim scriptSemaphore = new SemaphoreSlim(0, 1);

        private uint numberOfTriggersCompleted = 0;

        public TrainingScript()
        {
            this.uiDispatcher = Application.Current.Dispatcher;
            this.Resources = new ResourceDictionary();
            this.Triggers = new TrainingTriggersCollection();
        }

        public virtual void Execute()
        {
            if(this.Triggers == null || this.Triggers.Count == 0)
            {
                this.IsRunning = true;
                this.onCompleted();
                this.IsRunning = false;

                return;
            }

            this.scriptThread = new Thread((ThreadStart)this.script);

            this.IsRunning = true;
            this.scriptThread.Start();
        }

        public virtual void Dispose()
        {
            if(this.scriptThread != null)
            {
                this.scriptThread.Abort();
                this.scriptThread = null;
            }

            foreach (TrainingTriggerBase trigger in this.Triggers)
                trigger.Dispose();

            if(this.IsRunning)
            {
                this.onCompleted();
                this.IsRunning = false;
            }
        }

        private void script()
        {
            try
            {
                Queue<TrainingTriggerBase> triggers = new Queue<TrainingTriggerBase>(this.Triggers);
                if (triggers.Count > 0)
                {
                    this.nextTrigger = triggers.Dequeue();
                    this.nextTrigger.Started += this.triggerStarted;
                    this.nextTrigger.Completed += this.triggerStateChanged;
                    this.nextTrigger.Completed += this.triggerCompleted;
                    this.nextTrigger.EnsureConditionSatisfaction(this.triggerConditionSatisfied);

                    this.uiDispatcher.Invoke(delegate { }, DispatcherPriority.Loaded);

                    this.uiDispatcher.Invoke(this.nextTrigger.Prepare);
                }

                while(this.nextTrigger != null)
                {
                    this.scriptSemaphore.Wait();

                    if (this.currentTrigger != null)
                        this.uiDispatcher.Invoke(this.currentTrigger.Clear);

                    this.currentTrigger = this.nextTrigger;

                    this.uiDispatcher.Invoke(delegate { }, DispatcherPriority.Loaded);

                    if (triggers.Count > 0)
                    {
                        this.nextTrigger = triggers.Dequeue();
                        this.nextTrigger.Started += this.triggerStarted;
                        this.nextTrigger.Completed += this.triggerStateChanged;
                        this.nextTrigger.Completed += this.triggerCompleted;
                        this.nextTrigger.EnsureConditionSatisfaction(this.triggerConditionSatisfied);

                        this.uiDispatcher.Invoke(this.nextTrigger.Prepare);
                    }
                    else
                        this.nextTrigger = null;

                    this.uiDispatcher.Invoke(this.currentTrigger.Execute);
                }
            }
            catch (ThreadAbortException) { }
        }
        private void triggerConditionSatisfied(TrainingTriggerBase trigger)
        {
            if (this.TriggerConditionSatisfied != null)
                this.TriggerConditionSatisfied(trigger);

            this.triggerStateChanged(trigger);
        }
        private void triggerStateChanged(TrainingTriggerBase trigger)
        {
            if((this.currentTrigger != null && !this.currentTrigger.IsCompleted) || (this.nextTrigger != null && !this.nextTrigger.IsConditionSatisfied))
                return;

            this.scriptSemaphore.Release();
        }
        private void triggerStarted(TrainingTriggerBase trigger)
        {
            if (this.TriggerStarted != null)
                this.TriggerStarted(trigger);
        }
        private void triggerCompleted(TrainingTriggerBase trigger)
        {
            if (this.TriggerCompleted != null)
                this.TriggerCompleted(trigger);

            this.numberOfTriggersCompleted++;

            if(this.numberOfTriggersCompleted >= this.Triggers.Count)
            {
                if (this.currentTrigger != null)
                {
                    this.uiDispatcher.Invoke(this.currentTrigger.Clear);
                    this.currentTrigger = null;
                }

                this.IsRunning = false;
                this.uiDispatcher.Invoke(this.onCompleted);
            }
        }
        private void onCompleted()
        {
            if (this.Completed != null)
                this.Completed(this);
        }
    }
}
